package net.groupme.controller;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.groupme.api.API_CONSTANTS;
import net.groupme.controller.model.HtmlFormattedElement;
import net.groupme.controller.oauth.OAuthProtocol;
import net.groupme.controller.oauth.OAuthProvider;
import net.groupme.exceptions.GroupVisualizationException;
import net.groupme.exceptions.InsertFailedException;
import net.groupme.exceptions.UserNotExistingException;
import net.groupme.group.GroupManager;
import net.groupme.gui.constants.VIEW_VOCABULARY;
import net.groupme.gui.rendering.TagRenderer;
import net.groupme.logging.GroupMeLoggerFactory;
import net.groupme.logging.GroupMeNavigationLogger;
import net.groupme.logging.model.ActionIdentifier;
import net.groupme.model.Group;
import net.groupme.model.MODEL_CONSTANTS;
import net.groupme.model.ObjectFactory;
import net.groupme.model.OnlineStatus;
import net.groupme.model.Resource;
import net.groupme.model.ResourceVisualization;
import net.groupme.model.User;
import net.groupme.search.SEARCH_CONFIGURATION;
import net.groupme.search.SearchManager;
import net.groupme.search.strategy.model.WeightedTag;
import net.groupme.user.UserManager;
import net.groupme.user.UserManagerMessages;
import net.groupme.utils.HTTPUtilities;
import net.groupme.utils.ResourceTools;
import net.groupme.utils.sql.UserQueryUtility;
import net.oauth.OAuthAccessor;
import net.oauth.OAuthMessage;
import net.oauth.server.OAuthServlet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.groupme.rdf.sesame.ReadWriteUtility;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.util.CookieGenerator;


/**
 * created on 28/06/2007 
 * 
 * This GroupMe! controller handles requests like ... of groups.
 * 
 * TODO: Normally we should simply extend {@link SimpleFormController}...
 * 
 * @author Mischa Frank, <a href="mailto:code@planetfrank.net">code@planetfrank.net</a>
 * @author last edited by $Author: fabian $
 * 
 * @version $Revision: 1.29 $ $Date: 2009-04-22 18:28:55 $
 */
public class GroupManagementController implements Controller, ApplicationContextAware{

	/** Logger for this class and subclasses */
	protected final Log logger = LogFactory.getLog(getClass());
	
	
	/** the application context */
	ApplicationContext applicationContext = null;
	
	/** GroupMe Logger */
	private GroupMeNavigationLogger glogger = GroupMeLoggerFactory.getNavigationLogger();
	
	/**
	 * Set the ApplicationContext that this object runs in. 
	 * @param applicationContext the current {@link ApplicationContext} 
	 */
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	/* (non-Javadoc)
	 * @see org.springframework.web.servlet.mvc.Controller#handleRequest(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		String userRequestPath = request.getRequestURI();

    	String userRequest = userRequestPath.substring(userRequestPath.lastIndexOf("/") + 1);
    	if (request.getQueryString() != null) {
    		userRequestPath = userRequestPath + "?" + request.getQueryString();
    	}
    	logger.info("Requested Site/Action: " + userRequest );
		
		//output:
    	String output = getStringValue(request, API_CONSTANTS.PARAMETER_OUTPUT);
    	if(output == null){//Standard output format is HTML:
    		output = API_CONSTANTS.OUTPUT_HTML;
    	}
    	//create the MaV:
    	ModelAndView mav = new ModelAndView();
    	//switch cases that can be handled by this controller:
    	//(a) create a new group:
    	if(VIEW_VOCABULARY.ACTION_CREATE_GROUP.equals(userRequest)){
    		mav = createGroup(request, response);
    	}else if(VIEW_VOCABULARY.ACTION_LOGIN.equals(userRequest) || VIEW_VOCABULARY.ACTION_HEADER_LOGIN.equals(userRequest)){
    		mav = login(request, response);
    	}else if(VIEW_VOCABULARY.ACTION_SHOW_GROUP.equals(userRequest) || request.getRequestURI().contains(VIEW_VOCABULARY.REQUEST_TYPE_GROUP + "/" + HTTPUtilities.getStringValue(request, VIEW_VOCABULARY.PARAMETER_ID) + VIEW_VOCABULARY.RESTful_EDIT_PATTERN)){
    		mav = showGroup(request, response);
    		if(API_CONSTANTS.OUTPUT_RDF.equals(output) || API_CONSTANTS.OUTPUT_RDF.equals(HTTPUtilities.detectRequestedOutputFormat(request))){
    			mav.setViewName(API_CONSTANTS.VIEW_RDF_GROUP);
    		}
    	}else if(VIEW_VOCABULARY.ACTION_BROWSE_GROUPS.equals(userRequest) || request.getRequestURI().contains("/" + VIEW_VOCABULARY.REQUEST_TYPE_GROUP + "/")){
    		mav = browseGroups(request, response);
    		if(API_CONSTANTS.OUTPUT_RDF.equals(output) || API_CONSTANTS.OUTPUT_RDF.equals(HTTPUtilities.detectRequestedOutputFormat(request))){
    			mav.setViewName(API_CONSTANTS.VIEW_RDF_GROUP);
    		}
    	}
    	//add redirect infos
    	if (!VIEW_VOCABULARY.ACTION_LOGIN.equals(userRequest)){
    		if (VIEW_VOCABULARY.ACTION_HEADER_LOGIN.equals(userRequest)){
    			userRequestPath = request.getParameter(VIEW_VOCABULARY.PARAMETER_REDIRECT);
    		}
    		logger.info("Adding redirect infos: " + userRequestPath);
    		mav.addObject(VIEW_VOCABULARY.PARAMETER_REDIRECT, userRequestPath);
    	}
		return mav;
	}
	
	 /**
     * This method handles login requests.
	 * 
	 * @param request the HTTP request
	 * @param response the HTTP response
	 * 
     * @return the Spring {@link ModelAndView} object which includes the actual user object and 
     * points to a view identifier
     */
    private ModelAndView login(HttpServletRequest request, HttpServletResponse response){
    	//username, password:
    	String username = getStringValue(request, VIEW_VOCABULARY.PARAMETER_USERNAME);
    	String password = getStringValue(request, VIEW_VOCABULARY.PARAMETER_PASSWORD);
    	logger.info("username: " + username + " - password: " + password);
    	
    	ModelAndView mav = new ModelAndView();
    	
    	UserManager manager = (UserManager) applicationContext.getBean(VIEW_VOCABULARY.USER_MANGER, UserManager.class);
    	UserManagerMessages result = manager.login(username, password);
    	logger.info("User: " + username + " logged in.");
    	if(UserManagerMessages.LOGIN_SUCCESSFUL.equals(result)){    		
    		//cookie stuff:
    		String permanentLogin = getStringValue(request, VIEW_VOCABULARY.PARAMETER_KEEP_PERMANENT_LOGGED_IN);
    		String credential = null;
    		if(permanentLogin != null){
    			//user:
    			CookieGenerator cg = new CookieGenerator();
    			cg.setCookieName(VIEW_VOCABULARY.COOKIE_PERMANENT_LOGIN_USER_ID);
    			cg.setCookieMaxAge(VIEW_VOCABULARY.COOKIE_PERMANENT_LOGIN_LIFETIME);
    			cg.addCookie(response, manager.getUser().getId());
    			
    			//credential:
    			credential = manager.getUser().hashCode()+"";
    			cg = new CookieGenerator();
    			cg.setCookieName(VIEW_VOCABULARY.COOKIE_PERMANENT_LOGIN_CREDENTIAL);
    			cg.setCookieMaxAge(VIEW_VOCABULARY.COOKIE_PERMANENT_LOGIN_LIFETIME);
    			cg.addCookie(response, credential);
    		
    			logger.info("Permanent login - Cookies have to be enabled...");
    		}
			//update online status:
    		try {
				UserQueryUtility.updateUserStatus(manager.getUser().getId(), OnlineStatus.ONLINE, credential);
			} catch (InsertFailedException e) {
				logger.info("Updating online status failed!");
				e.printStackTrace();
			}
       		mav.setViewName(VIEW_VOCABULARY.VIEW_SUCCESSFUL_LOGGED_IN);
    	}else {
    		mav.setViewName(VIEW_VOCABULARY.VIEW_LOGIN_FAILED);
    		
    		if(UserManagerMessages.PASSWORD_INCORRECT.equals(result)){
    			mav.addObject(VIEW_VOCABULARY.STATUS_LOGIN, "Password incorrect.");
    			mav.addObject(VIEW_VOCABULARY.PARAMETER_USERNAME, username);
    		}else if(UserManagerMessages.PASSWORD_INCORRECT.equals(result)){
    			mav.addObject(VIEW_VOCABULARY.STATUS_LOGIN, "Username does not exist.");
    		}else{
    			mav.addObject(VIEW_VOCABULARY.STATUS_LOGIN, "Login failed. Please contact us if the problem persists.");
    		}
    		if(VIEW_VOCABULARY.ACTION_LOGIN_VIA_BOOKMARKLET.equals(getStringValue(request, VIEW_VOCABULARY.PARAMETER_ACTION))){
    			mav.setViewName(VIEW_VOCABULARY.VIEW_ADD_TO_GROUP_VIA_BOOKMARKLET);
        	}
    	}
    	//login from admin view?
    	if(request.getRequestURI().contains(VIEW_VOCABULARY.ACTION_ADMIN_CONTEXTPATH)){
    		mav.setViewName(VIEW_VOCABULARY.VIEW_ADMIN);
    	}
		return mav;
    }
    
	/**
	 * Initiates the creation of a new group by redirecting this request to the {@link GroupManager} instance
	 * 
	 * @param request the HTTP request
	 * @param response the HTTP response
	 * 
	 * @return the Spring {@link ModelAndView} object which includes the indicator for the <i>group creation view</i>
	 */
	private ModelAndView createGroup(HttpServletRequest request, HttpServletResponse response){
		String name = getStringValue(request, VIEW_VOCABULARY.PARAMETER_NAME);
		String description = getStringValue(request, VIEW_VOCABULARY.PARAMETER_DESCRIPTION);
		
		ModelAndView mav = new ModelAndView();   
		boolean success = false;
		GroupManager groupManager = (GroupManager) applicationContext.getBean(VIEW_VOCABULARY.GROUP_MANGER, GroupManager.class);
		UserManager userManager = (UserManager) applicationContext.getBean(VIEW_VOCABULARY.USER_MANGER, UserManager.class);
		if (userManager.getUser() == null){
			mav.setViewName(VIEW_VOCABULARY.VIEW_HOME);
			return mav;
		}
		//create new Group
		success = groupManager.createNewGroup(name, description);

		
		if(success){
			mav.addObject(VIEW_VOCABULARY.PARAMETER_ID, groupManager.getGroupID());
			mav.setViewName(VIEW_VOCABULARY.VIEW_GROUP);
			logger.info("AAAAAAAAAAA: " + mav.getViewName());
		}else{
			mav.setViewName(VIEW_VOCABULARY.VIEW_GROUP_CREATION_FAILED);
			mav.addObject(VIEW_VOCABULARY.STATUS_GROUP_MANAGEMENT, "Creation of group failed! Please contact us if this problem persists");
		}
		logger.info("Creation of group '" + name + "' with description '" + description + "' was " + (success ? "" : "not") + " successful");
		return mav;
	}
	
	/**
	 * Setting the GroupManager to the chosen Group.
	 * 
	 * @param request the HTTP request
	 * @param response the HTTP response
	 * 
	 * @return the Spring {@link ModelAndView} object which includes the indicator for the <i>group view</i>
	 */
	private ModelAndView showGroup(HttpServletRequest request, HttpServletResponse response) throws Exception{
		ModelAndView mav = new ModelAndView(); 
		GroupManager groupManager = (GroupManager) applicationContext.getBean(VIEW_VOCABULARY.GROUP_MANGER, GroupManager.class);
		UserManager userManager = (UserManager) applicationContext.getBean(VIEW_VOCABULARY.USER_MANGER, UserManager.class);
		if (userManager.getUser() == null){
			mav.setViewName(VIEW_VOCABULARY.VIEW_HOME);
			return mav;
		}
		String id = getGroupId(request);
		
//		Does the given group exist?
		if(!ResourceTools.doesResourceWithGivenIdExists(id, true)){
			mav.setViewName(VIEW_VOCABULARY.VIEW_GENERAL_ERROR);
			return mav;
		}

		//then set the Group
		groupManager.setGroup(ObjectFactory.getGroup(id));
		//then set the GroupVisualization
		try {
			groupManager.setGroupVisualization(groupManager.getGroup().getActualGroupVisualization(userManager.getUser().getId()));
		} catch (GroupVisualizationException e) {
			// TODO Entsprechend Reagieren bei Exception
			logger.error(e.getMessage(), e);
		}
		logger.info("Gruppe mit URI: " + groupManager.getGroup().getURI() + " wird visualisiert.");
		mav.addObject(VIEW_VOCABULARY.PARAMETER_ID, id);
		mav.addObject(VIEW_VOCABULARY.PARAMETER_VIEW, VIEW_VOCABULARY.VIEW_GROUP);
		mav.setViewName(VIEW_VOCABULARY.VIEW_GROUP);
		
		String output = getStringValue(request, API_CONSTANTS.PARAMETER_OUTPUT);
		if(output != null && API_CONSTANTS.OUTPUT_HTML_IFRAME.equals(output)){
    		mav.setViewName(VIEW_VOCABULARY.VIEW_GROUP_EDIT_AS_IFRAME);
    	}
		
		
		//begin groupme-logging:
		glogger.logBrowsing(userManager.getUser(), request.getSession().getId(), ActionIdentifier.RESOURCE_CLICK, groupManager.getGroup());
		//end groupme-logging
		
		return mav;
	}
    	
	/**
	 * Login the user, whose id is stored in the properties of the accessor.
	 * @param accessor the OAuth accessor
	 * @return the user manager instance where the user, whose id is stored in the properties of the accessor, is set
	 */
	private UserManager loginUser(OAuthAccessor accessor){
		UserManager um = (UserManager) this.applicationContext.getBean(VIEW_VOCABULARY.USER_MANGER, UserManager.class);
		try {
			um.setUser(ObjectFactory.getUser(new Integer(accessor.getProperty(OAuthProtocol.USER).toString())));
		} catch (NumberFormatException e) {
			e.printStackTrace();
		} catch (UserNotExistingException e) {
			e.printStackTrace();
		}
		
		return um;
	}
	
	/**
	 * API method for adding a tag or resource to a group.
	 * @param request
	 * @param response
	 * @return
	 */
	
	
	/**
	 * 
	 * 
	 * @param request the HTTP request
	 * @param response the HTTP response
	 * 
	 * @return the Spring {@link ModelAndView} object which includes the indicator for the <i>group view</i>
	 */
	private ModelAndView browseGroups(HttpServletRequest request, HttpServletResponse response) throws Exception{
		String id = getStringValue(request, VIEW_VOCABULARY.PARAMETER_ID);
		if (id == null){
			id="default";
		}else{
			//begin groupme-logging:
			User user = ((UserManager) applicationContext.getBean(VIEW_VOCABULARY.USER_MANGER, UserManager.class)).getUser();
			String sessionID = request.getSession().getId();
			//glogger.logBrowsing(user, sessionID, ActionIdentifier.RESOURCE_CLICK, ObjectFactory.getGroup(id));
			//end groupme-logging
		}
		ModelAndView mav = new ModelAndView();
		
//		Does the given group exist?
		if(!ResourceTools.doesResourceWithGivenIdExists(id, true) && id != "default"){
			mav.setViewName(VIEW_VOCABULARY.VIEW_GENERAL_ERROR);
			return mav;
		}
		mav.addObject(VIEW_VOCABULARY.PARAMETER_ID, id);
		mav.setViewName(VIEW_VOCABULARY.VIEW_BROWSE_GROUPS);
		
		//SUCHMASCHINEN-FREUNDLICHKEIT:
		//Tags + TagCloud der Gruppe berechnen:
		GroupManager gm = (GroupManager) applicationContext.getBean(VIEW_VOCABULARY.GROUP_MANGER, GroupManager.class);
		if(id != "default"){
			gm.setGroup(ObjectFactory.getGroup(id));
		}
		Group group = gm.getGroup();
		if(group != null){
			logger.info("Starting to compute tag cloud for: " + group.getName());
			SearchManager searchManager =(SearchManager) applicationContext.getBean(VIEW_VOCABULARY.GROUPME_SEARCH_MANGER, SearchManager.class);
			List<WeightedTag> weightedTags = searchManager.computeTagCloud(group, SEARCH_CONFIGURATION.TAG_CLOUD_MIDDLE_MAX_NUMBER_OF_TAGS);
			
			//Tags der Gruppe:
			mav.addObject("taglistAsString", getTagsFromTagCloud(weightedTags));
			HtmlFormattedElement tagCloud = new HtmlFormattedElement(null, new TagRenderer().renderTagCloud(weightedTags));
			
			//TagCloud der Gruppe:
			mav.addObject("tagcloud", tagCloud);
			
			//Description
			mav.addObject("descriptionString", gm.getGroup().getDescription());
						
			//Name der Gruppe
			mav.addObject("nameOfGroup", getNameOfGroup(gm.getGroup().getName()));
			
			//Name der View
			mav.addObject(VIEW_VOCABULARY.PARAMETER_VIEW, VIEW_VOCABULARY.VIEW_BROWSE_GROUPS);
		}
		
		/* API stuff */
		//output?
    	String output = getStringValue(request, API_CONSTANTS.PARAMETER_OUTPUT);
    	if(output == null){//Standard output format is HTML:
    		output = API_CONSTANTS.OUTPUT_HTML;
    	}else if(API_CONSTANTS.OUTPUT_HTML_IFRAME.equals(output)){
    		mav.setViewName(VIEW_VOCABULARY.VIEW_GROUP_AS_IFRAME);
    	}
    	if("POST".equals(request.getMethod())
    			|| "PUT".equals(request.getMethod())){//all POST and PUT requests are interpreted as API calls...

    		//check authorization:
    		try{
                OAuthMessage requestMessage = OAuthServlet.getMessage(request, null);
                requestMessage = OAuthProvider.prepare(requestMessage);
                OAuthAccessor accessor = OAuthProvider.getAccessor(requestMessage);
                if(gm.getManager() == null){
        			gm.setUserManager(loginUser(accessor));
        		}
                logger.info("OAuth request message when trying to add the resource: " + requestMessage);
                //validation will throw an exception if the client application is not authorized:
                OAuthProvider.VALIDATOR.validateMessage(requestMessage, accessor);
                
                //do what the client wants to do:
                //List<org.groupme.model.light.Group> groupsToCreate = ReadWriteUtility.deserializeFromRDF_XML(getStringValue(request, API_CONSTANTS.RDF_CONTENT));
                //first check whether the client posted a new new resource:
                
                List<org.groupme.model.light.Resource> resourcesToAdd = ReadWriteUtility.deserializeResourceFromRDF_XML(getStringValue(request, API_CONSTANTS.RDF_CONTENT));
                for(org.groupme.model.light.Resource resourceToAdd: resourcesToAdd){
                	//create group:
                	ResourceVisualization rv =  gm.addResource(resourceToAdd.name, resourceToAdd.description, HTTPUtilities.detectContentType(resourceToAdd.originalUrl), resourceToAdd.originalUrl.toString());
                	
                	//add tags of the resource:
                	for(String resourceTag: resourceToAdd.tags){
                		gm.addTagToResource(resourceTag, rv.getResource().getId());
                	}
                }
                //response.setStatus(HttpServletResponse.SC_CREATED);
    			mav.setViewName(API_CONSTANTS.VIEW_RDF_GROUP);
    			return mav;
                
            } catch (Exception e){
            	e.printStackTrace();
                OAuthProvider.handleException(e, request, response, false);
            }
    	}
    	
		return mav;
	}

	
	//method which returns a string with the tags of a group generated by the tag cloud
	private String getTagsFromTagCloud(List<WeightedTag> weightedTags){
		String tags = "";
		for(WeightedTag wt: weightedTags){
			tags += wt.tag.getKeyword() + ", ";
		}
		return tags;
	}
	
	//proofs if the name of a group is not empty; 
	//used to display a headline in every case(even if the groupname is empty or no group is selected in the group-browsing view
	private String getNameOfGroup(String nameOfGroup){
		if (nameOfGroup == "" || nameOfGroup == null){
			return "Explore the GroupMe! Groups";
		}
		return nameOfGroup;
	}
	

	
	
	/**
     * Very small helper function to extract the ID of a group from HttpServletRequest.
     * @param request the {@link HttpServletRequest} object
     * @return the group id as a String
     */
    private String getGroupId(HttpServletRequest request){
    	return getStringValue(request, VIEW_VOCABULARY.PARAMETER_ID);
    }
    
	/**
     * Very small helper function to extract a given parameter or attribute from a HttpServletRequest.
     * @param request the {@link HttpServletRequest} object
     * @param parameter the parameter
     * @return the value of the parameter as a String
     */
    private String getStringValue(HttpServletRequest request, String parameter){
    	String value = request.getParameter(parameter);
    	if(value == null && request.getAttribute(parameter) != null){ //normally this is not accessed but we do our best to extract the value.. 
    		value = request.getAttribute(parameter).toString();
    	}
    	return value;
    }
    
    /**
     * Checks whether the given user is the owner of the group and allowed to add resources.
     * @param user
     * @param group 
     * @return <code>true</code> if the given user is the owner of the group and allowed to add resources
     */
    private boolean allowedToAddResource(User user, Group group){
    	boolean allowed = false;
    	for(User u: group.getUsers(MODEL_CONSTANTS.ROLE_OWNER)){
    		if(u.getId().equals(user.getId())){
    			allowed = true;
    		}
    	}
    	return allowed;
    }
    


}
